/**
 * Project: d20Campaigner
 * Created: Sep 4, 2007 by bebopJMM
 */
package org.rollinitiative.d20;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * The Adjustment captures a singular adjustment to a modifiable score (AdjustableValue), such as an
 * Ability, Skill, or Save.
 * 
 * @author bebopJMM
 */
public class Adjustment
{

    static final Log LOG_ = LogFactory.getLog(Adjustment.class);

    public enum AdjustmentSource {
        INHERENT, EQUIPMENT, EFFECT
    };

    /**
     * Listeners to changes in the Adjustment
     */
    List<AdjustmentListener> subscribers_;

    /**
     * Value of the adjustment, which can change over time
     */
    int value_;

    /**
     * Limit of the adjustment
     */
    int limit_ = Integer.MAX_VALUE;

    final AdjustmentSource type_;

    final String description_;


    /**
     * Instantiates a new Adjustment with an empty list of subscribers
     * 
     * @param type AdjustmentType for the new Adjustment
     * @param value initial value for the new Adjustment
     * @param description textual description of the Adjustment
     */
    public Adjustment(AdjustmentSource type, int value, String description)
    {
        this.value_ = value;
        this.type_ = type;
        this.description_ = description;
        subscribers_ = new ArrayList<AdjustmentListener>();
    }


    /**
     * @return the current value of the Adjustment
     */
    public int getValue()
    {
        if (value_ <= limit_)
            return value_;
        else
            return limit_;
    }


    /**
     * This method updates the value of this Adjustment and notifies all subscribers of the change.
     * 
     * @param value new value for the adjustment
     */
    public void setValue(int value)
    {
        this.value_ = value;
        notifyOfChange();
    }


    /**
     * @return the current limit being applied to the adjustment. Integer.MAX_VALUE is essentially
     *         no limit.
     */
    public int getLimit()
    {
        return limit_;
    }


    /**
     * This method updates the limit of this Adjustment and notifies all subscribers of the change.
     * 
     * @param limit new limit to be applied
     */
    public void setLimit(int limit)
    {
        this.limit_ = limit;
        notifyOfChange();
    }


    /**
     * @return the AdjustementType of the Adjustment
     */
    public AdjustmentSource getType()
    {
        return type_;
    }


    /**
     * @return the description of the Adjustment
     */
    public String getDescription()
    {
        return description_;
    }


    /**
     * Add the listener as a subscriber to changes in the adjustment if it hasn't already
     * subscribed.
     * 
     * @param listener AdjustmentListener to add.
     */
    public synchronized void subscribe(AdjustmentListener listener)
    {
        if (!subscribers_.contains(listener)) {
            subscribers_.add(listener);
        }
    }


    /**
     * Removes the listener as a subscriber to changes in the adjustment
     * 
     * @param listener AdjustmentListener to remove
     */
    public synchronized void unsubscribe(AdjustmentListener listener)
    {
        if (subscribers_.contains(listener)) {
            subscribers_.remove(listener);
        }
    }


    /**
     * This method notifies all AdjustmentListener subscribers of a change in the Adjustment's value
     * via the subcriber's valueChanged() method.
     * 
     * @see org.rollinitiative.d20.AdjustmentListener#valueChanged(org.rollinitiative.d20.Adjustment)
     */
    protected void notifyOfChange()
    {
        synchronized (subscribers_) {
            if (LOG_.isDebugEnabled()) {
                LOG_.debug("Notifying subscribers, total = " + subscribers_.size());
            }
            for (AdjustmentListener listener : subscribers_) {
                listener.valueChanged(this);
            }
        }
    }

}
